<?php
use Countable;
class MyCountable implements Countable {}
class MyOuterIterator implements OuterIterator {}
class MyRecursiveIterator implements RecursiveIterator {}
class MySeekableIterator implements \SeekableIterator {}
class MySerializable implements Serializable {
    public function __sleep() {}
    public function __wakeup() {}
}
class MySplObserver implements SplObserver {}
class MySplSubject implements SplSubject {}
class MyJsonSerializable implements JsonSerializable {}
class MySessionHandlerInterface implements SessionHandlerInterface {}

// Test multiple interfaces
class MyMultiple implements SplSubject, SeekableIterator, Countable {}

// Test case-insensitive matching
class MyUppercase implements COUNTABLE {}
class MyLowercase implements countable {}

// These shouldn't throw errors.
class MyJsonSerializable implements JsonSerializableSomething {}
class MyJsonSerializable implements myNameSpace\JsonSerializable {}

// Test anonymous class support.
$a = new class implements SeekableIterator {};
$b = new class implements \Serializable {
    public function __sleep() {}
    public function __wakeup() {}
};

// Additional new interfaces.
class MyTraversable implements Traversable {}
class MyDateTimeInterface implements DateTimeInterface {}
class MyThrowable implements Throwable {}

class MyClass {
    // Interfaces as typehints.
    function CountableTypeHint( Countable $a ) {}
    function OuterIteratorTypeHint( OuterIterator $a ) {}
    function RecursiveIteratorTypeHint( RecursiveIterator $a ) {}
    function SeekableIteratorTypeHint( SeekableIterator $a ) {}
    function SerializableTypeHintA( Serializable $a ) {}
    function SplObserverTypeHintA( SplObserver $a ) {}
    function SplSubjectTypeHintA( SplSubject $a ) {}
    function JsonSerializableTypeHint( JsonSerializable $a ) {}
    function SessionHandlerInterfaceTypeHint( SessionHandlerInterface $a ) {}
    function TraversableTypeHintA( Traversable $a ) {}
    function DateTimeInterfaceTypeHintA( DateTimeInterface $a ) {}
    function ThrowableTypeHintA( Throwable $a ) {}

    // Namespaced interfaces as typehints
    function SerializableTypeHintB( \Serializable $a ) {} // Error: global namespace.
    function SplObserverTypeHintB( myNameSpace\SplObserver $a ) {} // Ok.
    function SplSubjectTypeHintB( \some\other\SplSubject $a ) {} // Ok.

    // Interfaces as nullable typehints (PHP 7.1+).
    function TraversableTypeHintB( ?Traversable $a ) {}
    function DateTimeInterfaceTypeHintB( ?\DateTimeInterface $a ) {}
    function ThrowableTypeHintB( ?Throwable $a ) {}

    // Function with multiple typehinted parameters.
    function MultipleTypeHints (OuterIterator $a, ?int $b, SplObserver $c, ?\RecursiveIterator $d) {}
}

// Interfaces in type hints in anonymous functions.
function ( SplSubject $a ) {};
function(\Serializable $a) {};
function ( ?Traversable $a ) {};
function(myNameSpace\SplObserver $a) {}; // Ok.

// Additional new interfaces.
class MyReflector implements Reflector {}

// Test recognition of return type declarations.
function CountableReturnTypeHint( $a ) : COUNTABLE {}
function TraversableReturnTypeHint( $a ) : ?Traversable {}
function DateTimeInterfaceReturnTypeHint( $a ) : \DateTimeInterface {}
function($a):throwable {};

// Test against false positives for return type declarations.
function SeekableIteratorReturnTypeHint( $a ) : SomeNS\SeekableIterator {}
function SessionHandlerInterfaceReturnTypeHint( $a ) : ?\MyNS\SessionHandlerInterface {}
function SplSubjectReturnTypeHint( $a ) : \MyNS\SplSubject\AnotherInterface {}

// More new interfaces.
class MySessionIdInterface implements SessionIdInterface {}
class MySessionUpdateTimestampHandlerInterface implements SessionUpdateTimestampHandlerInterface {}

try {
} catch (Throwable $e) {
}

// Multi-catch.
try {
} catch (Throwable | \RuntimeException $e) {
}

// Global namespace, should throw error.
try {
} catch (\Throwable $e) {
}

// Namespaced, should be ignored.
try {
} catch (\My\Except\Throwable $e) {
}

// PHP 8.0 new interfaces.
function StringableTypeHint( Stringable $a ) {}

// Test interfaces extending interfaces.
interface DoesNotExtend {}
interface MyReflection extends Reflector {}
interface MySession extends SessionABC, SessionIdInterface {}

interface NewSerial extends \Serializable {
    public function __sleep();
    public function __wakeup();
}

// Function declarations nested in methods are not magic.
class Nested implements Serializable {
    public function declaredNested() {
        function __sleep() {}
        function __wakeup() {}
    }
}

// Test error line precision.
function errorLinePrecisionCheck(
    JsonSerializable $param
) : JsonSerializable {}

// Test support for PHP 7.4 property types.
public Throwable $var; // Ignore, not a property, parse error.

$anon = new class() {
    public $untypedProperty;
    public SessionUpdateTimestampHandlerInterface $property;
};

// Test support for PHP 7.4 arrow functions.
$fn = fn(SessionIdInterface $param) => $param;
$fn = fn($param): SessionHandlerInterface => $param;

// Test support for PHP 8.0 union types.
class UnionTypes {
    public NotATarget|AlsoNotATarget $okay;

    public Countable|SplObserver $property;
    public function paramType(OuterIterator|RecursiveIterator $param) {}
    public function returnType($param) : SessionHandlerInterface|JsonSerializable {}
}

// Test support for PHP 8.1 intersection types.
class IntersectionTypes {
    public function paramTypeA(NotATarget&AlsoNotATarget $okay) {}

    public Throwable&SessionUpdateTimestampHandlerInterface $propertyA, $propertyB;
    public function paramTypeB(SeekableIterator&SplSubject $param) {}
    public function returnType($param) : Reflector&Traversable {}
}

// Test very invalid type.
function InvalidType($param): ?\&\SomeType {}

// Test invalid catch.
try {
} catch ($e) {}

// Test support with PHP 8.0 constructor property promotion.
class ConstructorProp {
    public function __construct(
        protected $property,
        public Reflector | AllGood $reflProp,
        private Stringable $stringProp,
    ) {}
}

// Test support with PHP 8.0 non-capturing catch.
try {
} catch (SomeException) {
} catch (MyException | Throwable | AnotherException) {}

// Test support for PHP 8.1 enums implementing interfaces.
enum NoImplements {}
enum MyPlainEnum implements JsonSerializable {}
enum MyBackedEnum: string implements NotATarget, Serializable, ArrayAccess {
    public function __sleep() {}
    public function __wakeup() {}
}

class MyDom implements DOMChildNode, DOMParentNode {}

function ExpectsEnumParams(UnitEnum $enum1, BackedEnum $enum2) {}

function useRandomExtension( Random\Engine $paramA, Random\CryptoSafeEngine $paramB ) {}

function CheckAllTypeParts(
    NotATarget|Stringable $a
) : NotATarget|DOMParentNode {}

// Test support for PHP 8.2 DNF types.
class DNFTypes {
    public function paramTypeA(NotATarget&AlsoNotATarget $okay) {}

    public true|(DOMChildNode&DOMParentNode) $property;
    public function paramTypeB((UnitEnum&BackedEnum)|string $param) {}
    public function returnType($param) : (Stringable&DOMChildNode)|(A&B) {}
}

// Safeguard correct handling of PHP 8.3 readonly anonymous class.
$anon = new readonly class implements \Random\Engine {};

// Global constants can't be typed. Ignore.
const FOO = 10;

// Test support for PHP 8.3 typed constants.
class TypedConstants {
    const UNTYPED = 10;
    protected const UnitEnum CLASS_TYPE = new ClassName();
    private const ?BackedEnum NULLABLE_CLASS_TYPE = null;
    protected const \Random\CryptoSafeEngine FQN_INTERFACE = new ClassName;

    // Union types are supported.
    private final const RecursiveIterator|\SeekableIterator UNION_TWO_CLASSES = new MyClassA;

    // Intersection types are supported.
    public const JsonSerializable&\SessionHandlerInterface INTERSECTION_TYPE = new MyClassA;

    // DNF types are supported.
    public const (Random\Engine&\Random\CryptoSafeEngine)|null DNF_TYPE = new MyClassA;
}

// Safeguard against false positives on namespaced names.
class NamespaceRelative implements namespace\OuterIterator {}
class PartiallyQualified implements Partially\Qualified\SessionHandlerInterface {}
class FullyQualified implements \Fully\Qualified\SessionUpdateTimestampHandlerInterface {}

interface MyNamespaceRelative extends namespace\DOMChildNode {}
interface MyPartiallyQualified extends Partially\Qualified\Random\Engine {}
interface MyFullyQualified extends \Fully\Qualified\SplObserver {}

class NamespacedNames {
    const namespace\UnitEnum NAME = 10;
    public readonly Partially\Qualified\JsonSerializable $property = null;
    function callMe(
        namespace\DOMParentNode $paramA,
        \Fully\Qualified\RecursiveIterator $paramB,
        Partially\Qualified\SeekableIterator $paramC,
    ): \Fully\Qualified\Reflector {
    }
}
